IAT HOOK

1、IAT钩取工作原理

进程的IAT中保存着程序中调用的API的地址,IAT钩取通过修改IAT中保存的API地址来钩取某个API。
YDaMdO.png
图中描述的是计算器(calc.exe)进程正常调用user32.SetWindowTextW()API的情形。地址01001110属于IAT区域,程序开始运行时,PE装载器会将user32.SetWindowTextW()API地址(77D0960E)记录到该地址(01001110)。01002628地址处的CALL DWORD PTR [01001110]指令最终会调用保存在01001110地址(77D0960E)处的函数,直接等同于CALL 77D0960E命令。
执行完地址01002628处的CALL命令后,运行将转移到user32.SetWindowTextW()函数的起始地址(77D0960E)处,执行完user32.SetWindowTextW()后返回。
下图是IAT被钩取后计算器进程的运行过程。
YDdOvn.png
hookiat.dll文件中通过了了名为MySetWindowTextW()的钩取函数(10001000)。地址01002628处的CALL指令执行后,会跟踪进入10001000,而不是01001110。经过一系列的处理后,执行1000107D处的CALL命令,转到原user32.SetWindowTextW()函数的起始地址。user32.SetWindowTextW()API执行完毕后,执行会返回到hookiat.dll的1000107D地址的下雨天指令,然后返回到01002628地址的下一条指令继续执行。

2、演示示例

(1)calc.exe
(2)hookiat.dll
(3)InjectDll.exe

1
2
InjectDll.exe i pID hookiat.dll //钩取命令
InjectDll.exe e pID hookiat.dll //脱钩命令

钩取后程序运行的样子:
YDrwT0.png

3、源代码分析

1、DllMain()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpvReserved)
{
switch( fdwReason )
{
case DLL_PROCESS_ATTACH :
// 保存原始API地址
g_pOrgFunc = GetProcAddress(GetModuleHandle(L"user32.dll"),
"SetWindowTextW");

// # hook
// 用hookiat.MySetWindowText()钩取user32.SetWindowTextW()
hook_iat("user32.dll", g_pOrgFunc, (PROC)MySetWindowTextW);
break;

case DLL_PROCESS_DETACH :
// # unhook
// 将IAT恢复
hook_iat("user32.dll", (PROC)MySetWindowTextW, g_pOrgFunc);
break;
}

return TRUE;
}

2、MySetWindowTextW()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
BOOL WINAPI MySetWindowTextW(HWND hWnd, LPWSTR lpString)
{
wchar_t* pNum = L"零一二三四五六七八九";
wchar_t temp[2] = {0,};
int i = 0, nLen = 0, nIndex = 0;

nLen = wcslen(lpString);
for(i = 0; i < nLen; i++)
{
// 将阿拉伯数字转换为中文数字
if( L'0' <= lpString[i] && lpString[i] <= L'9' )
{
temp[0] = lpString[i];
nIndex = _wtoi(temp);
lpString[i] = pNum[nIndex];
}
}
// 调用user32.SetWindowTextW()API
return ((PFSETWINDOWTEXTW)g_pOrgFunc)(hWnd, lpString);
}

SetWindowTextW()API定义如下:

1
2
3
4
BOOL SetWindowTextW(
HWND hwnd,
LPCTSTR lpString
);

它有两个参数,第一个为窗口句柄(hWnd),第二个参数为字符串指针(lpString)。
阿拉伯数字字符串转换为中文数字字符串情形如下:
YDygd1.png

3、hook_iat()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
BOOL hook_iat(LPCSTR szDllName, PROC pfnOrg, PROC pfnNew)
{
HMODULE hMod;
LPCSTR szLibName;
PIMAGE_IMPORT_DESCRIPTOR pImportDesc;
PIMAGE_THUNK_DATA pThunk;
DWORD dwOldProtect, dwRVA;
PBYTE pAddr;

// hMod, pAddr = ImageBase of calc.exe
// = VA to MZ signature (IMAGE_DOS_HEADER)
hMod = GetModuleHandle(NULL);
pAddr = (PBYTE)hMod;

// pAddr = VA to PE signature (IMAGE_NT_HEADERS)
pAddr += *((DWORD*)&pAddr[0x3C]);

// dwRVA = RVA to IMAGE_IMPORT_DESCRIPTOR Table
dwRVA = *((DWORD*)&pAddr[0x80]);

// pImportDesc = VA to IMAGE_IMPORT_DESCRIPTOR Table
pImportDesc = (PIMAGE_IMPORT_DESCRIPTOR)((DWORD)hMod+dwRVA);

for( ; pImportDesc->Name; pImportDesc++ )
{
// szLibName = VA to IMAGE_IMPORT_DESCRIPTOR.Name
szLibName = (LPCSTR)((DWORD)hMod + pImportDesc->Name);
if( !_stricmp(szLibName, szDllName) )
{
// pThunk = IMAGE_IMPORT_DESCRIPTOR.FirstThunk
// = VA to IAT(Import Address Table)
pThunk = (PIMAGE_THUNK_DATA)((DWORD)hMod +
pImportDesc->FirstThunk);

// pThunk->u1.Function = VA to API
for( ; pThunk->u1.Function; pThunk++ )
{
if( pThunk->u1.Function == (DWORD)pfnOrg )
{
VirtualProtect((LPVOID)&pThunk->u1.Function,
4,
PAGE_EXECUTE_READWRITE,
&dwOldProtect);

pThunk->u1.Function = (DWORD)pfnNew;

VirtualProtect((LPVOID)&pThunk->u1.Function,
4,
dwOldProtect,
&dwOldProtect);

return TRUE;
}
}
}
}

return FALSE;
}

hook_iat()函数的前半部分用来读取PE文件头信息,并查找IAT的位置。

1
2
3
4
5
hMod = GetModuleHandle(NULL); // hMod = ImageBase
pAddr = (PBYTE)hMod; // pAddr = IMAGE_NT_HEADERS
pAddr += *((DWORD*)&pAddr[0x3C]); // pAddr = "PE" signature
dwRVA = *((DWORD*)&pAddr[0x80]); // dwRVA = VA to IMAGE_IMPORT_DESCRIPTOR Table
pImportDesc = (PIMAGE_IMPORT_DESCRIPTOR)((DWORD)hMod+dwRVA);

找到IMAGE_IMPORT_DESCRIPTOR表后,用for循环遍历,查找user32.dll的IAT。pImportDesc->FirstThunk成员所指的就是IAT。然后再循环遍历,查找SetWindowTextW的位置。找到后,修改为MySetWindowTextW的地址。

1
pThunk->u1.Function = (DWORD)pfnNew;

这样,计算器代码调用user32.SetWindowTextW()API时,实际会先调用hookiat.MySetWindowTextW()函数。